package repository

import (
	"context"
	"github.com/ecodeclub/ekit/slice"
	"gorm.io/gorm"
	"sync/atomic"
	"time"
	"webook/interactive/domain"
	"webook/interactive/repository/cache"
	"webook/interactive/repository/dao"
	"webook/pkg/logger"
)

var (
	ErrRecordNotFound = gorm.ErrRecordNotFound //表示未找到
)

// 这里的名字脱离业务 偏向数据库
type InteractiveRepository interface {
	IncrReadCnt(ctx context.Context, biz string, bizId int64) error
	BatchIncrReadCnt(ctx context.Context, biz []string, bizId []int64) error
	IncrLikeCnt(ctx context.Context, biz string, bizId int64, uid int64) error
	DecrLikeCnt(ctx context.Context, biz string, bizId int64, uid int64) error
	AddCollectionItem(ctx context.Context, biz string, bizId int64, cid int64, uid int64) error
	Get(ctx context.Context, biz string, bizId int64) (domain.Interactive, error)
	Liked(ctx context.Context, biz string, bizId int64, uid int64) (bool, error)
	Collected(ctx context.Context, biz string, bizId int64, uid int64) (bool, error)
	GetByIds(ctx context.Context, biz string, ids []int64) ([]domain.Interactive, error)
	QueryTopK(ctx context.Context, biz string, rankCnt int64) ([]int, error)
}
type CachedInteractiveRepository struct {
	dao           dao.InteractiveDao
	cache         cache.InteractiveCache
	l             logger.LoggerV1
	topKByLikeCnt atomic.Value
	topKCacheTime time.Duration
	topKBiz       string
	topKCount     int
}

func (c *CachedInteractiveRepository) QueryTopK(ctx context.Context, biz string, rankCnt int64) ([]int, error) {
	cacheTopK := c.topKByLikeCnt.Load().([]int)
	var err error
	if len(cacheTopK) == 0 { //本地缓存没有的话去redis取
		cacheTopK, err = c.cache.QueryTopK(ctx, biz, rankCnt)
		c.topKByLikeCnt.Store(cacheTopK)
	}
	return cacheTopK, err
}
func (c *CachedInteractiveRepository) GetByIds(ctx context.Context, biz string, ids []int64) ([]domain.Interactive, error) {
	intrs, err := c.dao.GetByIds(ctx, biz, ids)
	if err != nil {
		return nil, err
	}
	return slice.Map(intrs, func(idx int, src dao.Interactive) domain.Interactive {
		return c.toDomain(src)
	}), nil
}
func (c *CachedInteractiveRepository) Get(ctx context.Context, biz string, bizId int64) (domain.Interactive, error) {
	intr, err := c.cache.Get(ctx, biz, bizId)
	if err == nil {
		return intr, nil
	}
	ie, err := c.dao.Get(ctx, biz, bizId)
	if err != nil {
		return domain.Interactive{}, err
	}
	res := c.toDomain(ie)
	err = c.cache.Set(ctx, res, biz, bizId)
	if err != nil {
		//记录日志
		c.l.Error("回写缓存失败", logger.String("biz", biz), logger.Int64("biz_id", bizId), logger.Error(err))
	}
	return res, nil
}

func (c *CachedInteractiveRepository) Liked(ctx context.Context, biz string, bizId int64, uid int64) (bool, error) {
	_, err := c.dao.GetLikedInfo(ctx, biz, bizId, uid)
	switch err {
	case nil:
		return true, nil
	case ErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (c *CachedInteractiveRepository) Collected(ctx context.Context, biz string, bizId int64, uid int64) (bool, error) {
	_, err := c.dao.GetCollectedInfo(ctx, biz, bizId, uid)
	switch err {
	case nil:
		return true, nil
	case ErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (c *CachedInteractiveRepository) AddCollectionItem(ctx context.Context, biz string, bizId int64, cid int64, uid int64) error {
	err := c.dao.InsertCollectionBiz(ctx, dao.UserCollectionBiz{
		Biz: biz, BizId: bizId, Cid: cid, Uid: uid,
	})
	if err != nil {
		return err
	}
	return c.cache.IncrCollectCntIfPresent(ctx, biz, bizId)
}

func (c *CachedInteractiveRepository) IncrLikeCnt(ctx context.Context, biz string, bizId int64, uid int64) error {
	err := c.dao.IncrLikeInfo(ctx, biz, bizId, uid)
	if err != nil {
		return err
	}
	//更新缓存
	err = c.cache.IncrLikeCntIfPresent(ctx, biz, bizId)
	if err != nil {
		return err
	}
	//job9 维护点赞数前n
	updateErr := c.cache.UpdateRankCountIfExist(ctx, biz, bizId, 1)
	if updateErr == cache.ErrKeyNotExist {
		intr, err := c.dao.Get(ctx, biz, bizId)
		if err != nil {
			//打印日志
			c.l.Warn("查询信息失败", logger.Error(err), logger.String("biz", biz), logger.Int64("bizId", bizId))
			return nil
		}
		err = c.cache.CreateRankCount(ctx, biz, bizId, int(intr.LikeCnt))
		if err != nil {
			//打印日志
			c.l.Warn("创建排名信息失败", logger.Error(err), logger.String("biz", biz), logger.Int64("bizId", bizId))
		}
	} else if updateErr != nil {
		c.l.Warn("更新排名信息失败", logger.Error(err), logger.String("biz", biz), logger.Int64("bizId", bizId))
	}
	return nil
}

func (c *CachedInteractiveRepository) DecrLikeCnt(ctx context.Context, biz string, bizId int64, uid int64) error {
	err := c.dao.DeleteLikeInfo(ctx, biz, bizId, uid)
	if err != nil {
		return err
	}
	//更新缓存
	return c.cache.DecrLikeCntIfPresent(ctx, biz, bizId)
}

func (c *CachedInteractiveRepository) IncrReadCnt(ctx context.Context, biz string, bizId int64) error {
	err := c.dao.IncrReadCnt(ctx, biz, bizId)
	if err != nil {
		return err
	}
	//更新缓存
	//部分失败 == 数据不一致问题 阅读数 其实感知不到
	return c.cache.IncrReadCntIfPresent(ctx, biz, bizId)
}

func (c *CachedInteractiveRepository) toDomain(ie dao.Interactive) domain.Interactive {
	return domain.Interactive{
		Biz:        ie.Biz,
		ReadCnt:    ie.ReadCnt,
		LikeCnt:    ie.LikeCnt,
		CollectCnt: ie.CollectCnt,
		BizId:      ie.BizId,
	}
}
func (c *CachedInteractiveRepository) BatchIncrReadCnt(ctx context.Context, biz []string, bizId []int64) error {
	err := c.dao.BatchIncrReadCnt(ctx, biz, bizId)
	if err != nil {
		return err
	}
	go func() { //批量操作 lua脚本或是pile管道 但是对性能提升不大
		for i := 0; i < len(biz); i++ {
			er := c.cache.IncrReadCntIfPresent(ctx, biz[i], bizId[i])
			if er != nil {
				// 记录日志
			}
		}
	}()
	return nil
}
func (c *CachedInteractiveRepository) UpdateTopKByLikeCnt() {
	ticker := time.NewTicker(c.topKCacheTime)
	for range ticker.C {
		res, err := c.QueryTopK(context.Background(), c.topKBiz, int64(c.topKCount))
		if err != nil {
			c.l.Error("topk查询缓存失败", logger.Error(err))
			continue
		}
		c.l.Info("topk本地缓存更新成功", logger.Int("长度", len(res)))
		c.topKByLikeCnt.Store(res)
	}
}
func NewCachedInteractiveRepository(dao dao.InteractiveDao, cache cache.InteractiveCache) InteractiveRepository {
	tmp := &CachedInteractiveRepository{
		dao:           dao,
		cache:         cache,
		l:             logger.NewNoOpLogger(),
		topKCacheTime: 5 * time.Minute,
		topKBiz:       "like",
		topKCount:     100,
	}
	tmp.topKByLikeCnt.Store([]int{})
	go tmp.UpdateTopKByLikeCnt()
	return tmp
}
